/*
 *  Reference.h
 *  NSE_SOLVER
 *
 *  Created by Patrick Zulian on 1/19/11.
 *  Copyright 2011 ICS - USI - Lugano. All rights reserved.
 *
 */

#ifndef NSE_SOLVER_REFERENCE_H
#define NSE_SOLVER_REFERENCE_H
#include <iostream>

#include "Base.h"


namespace nsesolver {
	NSE_SOLVER_DEBUG( static unsigned int GlobalReferenceCount = 0; );
		
template< class T >
class Reference {
private:
	class Count {		
	public: 
		~Count()
		{
			
			NSE_SOLVER_DEBUG(
						  if (GlobalReferenceCount == 0) {
							  std::cout << "No Memory Leaks with Reference class \n" << std::endl;
						  }
						  );
		}
		
		inline
		explicit Count(int n = 0)
		: n(n)
		{ 
			NSE_SOLVER_DEBUG( GlobalReferenceCount += n );
		}
		
		inline
		Count & increment()
		{
			NSE_SOLVER_DEBUG( GlobalReferenceCount++  );
			++n;
			return *this;
		}	
		
		inline
		Count & decrement()
		{
			NSE_SOLVER_DEBUG( GlobalReferenceCount-- );
			--n;
			return *this;
		}
		
		inline
		int amount() const
		{
			return n;
		}
		
		inline
		bool isZero() const
		{
			return n == 0;
		}
		
	private:
		int n;
	};
	
private:
	
	
	Count * count;
	T * object;
public:
	Reference()
	: count(new Count(0)), object(NULL)
	{ }
	
	
	Reference(const Reference &other)
	: count(other.count), object(other.object)
	{
		count->increment();
	}
	
	Reference(T * object)
	: count(new Count(1)), object(object)
	{ }
	
	~Reference()
	{
		count->decrement();
		if (count->isZero()) {
			delete object;
			delete count;
		}
	
	}
	
    inline bool isNull() const 
    {
        return object == NULL;
    }
    
	Reference & operator=(T * const object)
	{
		if (this->object == object) {
			return *this;
		}
		
		if (count->amount() > 0) {
			count->decrement();
			if (count->isZero()) {
				delete this->object;
				delete count;
			}
		}
		
		count = new Count(1);		
		this->object = object;
		return *this;
		
	}
	
	Reference & operator=(const Reference &other)
	{
		if (object == other.object) {
			return *this;
		}		
		
		if (count->amount() > 0) {
			count->decrement();
			if (count->isZero()) {
				delete object;
			}
		}
		
		count = other.count;
		count->increment();
		
		object = other.object;
		return *this;
	}
	
	Reference & swap(Reference &other)
	{
		if (object == other.object) {
			return *this;
		}
		
		using std::swap;
		swap(count, other.count);
		swap(object, other.object);
		
		return *this;
	}
	
	
	friend
	inline
	void swap(Reference &ref1, Reference &ref2)
	{
		ref1.swap(ref2);
	}
	
	
	friend
	inline
	bool operator == (const Reference &ref1, const Reference &ref2) 
	{
		return ref1.object == ref2.object;
	}
	
	inline 
	T & get()
	{
		return *object;
	}
	
	inline
	T * const operator->()
	{
		return object;
	}
	
	inline
	const T * const operator->() const
	{
		return object;
	}
	
	
	friend 
	inline
	const T & operator*(const Reference &ref)
	{
		NSE_SOLVER_ASSERT( ref.object != NULL );
		return *(ref.object);
	}
	
	friend 
	inline
	T & operator*(Reference &ref)
	{
		NSE_SOLVER_ASSERT( ref.object != NULL );
		return *(ref.object);
	}
	
	friend
	inline
	std::ostream & operator <<(std::ostream &os, const Reference &ref) 
	{
		os << *ref;
		return os;
	}

	
};


}

#endif
